#!usr/bin/python
# -*- coding: utf8 -*-
encoding='utf8'

import os, sys, re, codecs

## Start concatenation ##

class Defines(object):
    def __init__(self):
        self.defs = {}

    def append(self, define):
        key = define.keyword
        if self.defs.has_key(key):
            raise KeyError, '"%s" allready defined' % (key)
        else:
            self.defs[key] = define

    def exists(self, keyword):
        if self.defs.has_key(keyword):
            return True
        else:
            return False

    def remove(self, keyword):
        #print 'Defined:', ', '.join(self.defs.iterkeys())
        if self.defs.has_key(keyword):
            del self.defs[keyword]
        else:
            raise KeyError, '"%s" not defined. Can\'t undefine' % (keyword)

    def apply(self, line):
        for d in self.defs.itervalues():
            line = d.apply(line)
        return line

class Define(object):
    REPLACEMENT = 1
    MACROS      = 2
    FLAG        = 3
    ALPHANUMERIC = u'abcdefghijklmnopqrstuvwxyz_0123456789'
    def __init__(self):
        self.type    = 0
        self.keyword = None
        self.apply   = None
        self.parse   = None
        self.re_flag = re.compile(u'(?imu)#define\s+([a-z_0-9]+)\s*$')
        self.re_replacement = re.compile(u'(?imu)\s*#define\s+([a-z_0-9]+)\s+(.+?)$')
        self.re_macros = re.compile(u'(?imu)#define\s+([a-z_0-9]+)(\([^)]*\))\s*(.*?)$')
        self.re_strip_braces = re.compile(u'(?imu)^\((.+?)\)$')
        self.ident = re.compile(u'(?imu)\b([a-z_][a-z_0-9]+)\b')

    def match(self, line):
        mr = self.re_replacement.match(line)
        mm = self.re_macros.match(line)
        mf = self.re_flag.match(line)
        if mm:
            #print u'Macros:', line
            self.type  = self.MACROS
            self.parse = self.parse_as_macros
            self.apply = self.apply_macros
            m = mm
        elif mr:
            #print u'Replacement:', line
            self.type  = self.REPLACEMENT
            self.parse = self.parse_as_replacement
            self.apply = self.apply_replacement
            m = mr
        elif mf:
            #print u'Flag:', line
            self.type  = self.FLAG
            self.parse = self.parse_as_flag
            self.apply = self.apply_flag
            m = mf
        else:
            print u'#define not matched:', line
            return False
        self.parse(m)
        return True

    def parse_as_flag(self, regular):
        self.keyword = regular.group(1)
        self.replacement = u''
        self.find_keyword = re.compile(ur'(?u)\b%s\b' % (self.keyword))
        #print u'%s !' % ( self.keyword )

    def parse_as_replacement(self, regular):
        self.keyword = regular.group(1)
        self.replacement = regular.group(2).strip()
        self.find_keyword = re.compile(ur'(?u)\b%s\b' % (self.keyword))
        #print u'%s => %s' % ( self.keyword, self.replacement )

    def parse_as_macros(self, regular):
        # The think is to create array, where every even element will number
        # of replacement parameter. And every uneven -- substitutional string.
        self.keyword = regular.group(1)
        self.parameters = regular.group(2)
        self.replacement = regular.group(3).strip()
        pattern = ur'(?u)\b%s\b' % (self.keyword)
        self.find_keyword = re.compile(pattern)
        #print u'%s %s => %s (%s)' % ( self.keyword, self.parameters,
        #                              self.replacement, pattern )

        params = self.re_strip_braces.sub(ur'\1', self.parameters)
        self.param_list = [ a.strip() for a in params.split(u',') ]
        #print self.param_list

    def apply_flag(self, code):
        #print u'apply_replacement for', self.keyword
        code = self.find_keyword.sub(u'#error using flag as replacement', code)
        return code

    def apply_replacement(self, code):
        #print u'apply_replacement for', self.keyword
        code = self.find_keyword.sub(self.replacement, code)
        return code

    def apply_macros(self, code):
        #print u'apply_macros for', self.keyword
        start, prev_repl = 0, 0
        result = u''
        pos = code.find(self.keyword, start)
        code_len = len(code)
        while pos >= 0:
            possible = True
            if pos > 0:
                # Check, that previuos char is not alphanumeric
                if code[pos-1] in self.ALPHANUMERIC:
                    possible = False
                    #print 'Impossible: Previous is alphanumeric'
                    #print code[pos:pos + len(self.keyword)]
            if pos + len(self.keyword) < code_len:
                # Check, that next char is '('
                c = code[pos + len(self.keyword)]
                if code[pos + len(self.keyword)] != u'(':
                    possible = False
                    #print 'Impossible: Next is not "("', c
                    #print code[pos:pos + len(self.keyword)]
            else:
                print 'Impossible: Macros at the end'
                possible = False
            if possible:
                #prev_repl = start
                prev_pos = cur_pos = start = pos + len(self.keyword) + 1
                brace_level = 1
                values = []
                while brace_level > 0:
                    cur = code[cur_pos]
                    if brace_level == 1:
                        if cur == u',':
                            appending = code[prev_pos:cur_pos].strip()
                            values.append(appending)
                            prev_pos = cur_pos + 1
                        elif cur == u'(':
                            brace_level+= 1
                        elif cur == u')':
                            break
                    else:
                        if cur == u'(':
                            brace_level+= 1
                        elif cur == u')':
                            brace_level-= 1
                    cur_pos+= 1
                appending = code[prev_pos:cur_pos].strip()
                values.append(appending)
                if len(values) == len(self.param_list):
                    start = cur_pos + 1
                    # Replacement (woo-hoo)
                    repl = self.replacement
                    #print repl
                    for pid, p in enumerate(self.param_list):
                        pattern = ur'(?u)\b%s\b' % (p)
                        repl = re.sub(pattern, values[pid], repl)
                    #print 'Between:'
                    #print code[prev_repl:pos]
                    #print 'Replacement:'
                    #print repl
                    #print '--to result:---'
                    #print result
                    #print '---------------'
                    result+= code[prev_repl:pos]
                    result+= repl
                    prev_repl = cur_pos+1
                else:
                    print 'Impossible: different count of parameters:',\
                           len(values), len(self.param_list)
                    print values
            else:
                start = pos + len(self.keyword)
            pos = code.find(self.keyword, start)
            #print 'Step done prev_repl=%d, start=%d' % (prev_repl, start)
        result+= code[prev_repl:]
        return result

## Stop concatenation ##

class Undefiner(object):
    def __init__(self):
        pass

    def Find_defines_infile(self, filename):
        code = codecs.open(filename, 'r', 'cp1251').read()
        self.Find_defines(code)

    def Find_defines(self, code):
        code = self.Concat_lines(self.Remove_comments(code))
        start, endpos, keyword = 0, 0, u'#define'
        defines = []
        pos = code.find(keyword, start)
        nodefines = u''
        while pos >= 0:
            endpos = code.find(u'\n', pos)
            defines.append(code[pos:endpos])
            nodefines+= code[start:pos]
            start = endpos + 1
            pos = code.find(keyword, start)
        nodefines+= code[start:]
        #print u'\n\n'.join(defines)
        define = Define()
        defo = []
        for d in defines:
            if define.match(d):
                defo.append(define)
                define = Define()
        for d in defo:
            print '>>> Apply', d.keyword
            nodefines = d.apply(nodefines)
        print
        print u'='*20
        print nodefines

    def Remove_comments(self, code):
        may_change    = False
        incomment     = False
        block_comment = False
        result = u''
        for c in code:
            if incomment:
                if block_comment:
                    if c == u'*':
                        may_change = True
                    else:
                        if may_change:
                            if c == '/':
                                may_change = False
                                incomment  = False
                else:
                    if c == u'\n':
                        may_change = False
                        incomment  = False
            else:
                if may_change:
                    if c == u'*':
                        may_change = False
                        incomment = True
                        block_comment = True
                    elif c == u'/':
                        may_change = False
                        incomment = True
                        block_comment = False
                    else:
                        result+= u'/' + c
                else:
                    if c == u'/':
                        may_change = True
                    else:
                        result+= c
        return result

    def Concat_lines(self, code):
        maybe = False
        result = u''
        for c in code:
            if maybe:
                if c == u'\r':
                    pass
                elif c == '\n':
                    maybe = False
                else:
                    result+= u'\\' + c
                    maybe = False
            else:
                if c == u'\\':
                    maybe = True
                else:
                    result+= c
        return result

def test1():
    filename = 'tests/undefine2.cpp'
    code = codecs.open(filename, 'r', 'cp1251').read()
    p = Undefiner()
    p.Find_defines(code)

def main():
    test1()

if __name__ == '__main__':
    main()
